﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

#if !PCL
using System.Numerics;
#endif

namespace OmniKits
{
    using Utilities;

    partial class Helpers
    {
        public sealed class PermutationHelper { }
        public static PermutationHelper Permutation { get; } 

        #region for Int

        public static int ToInt(this PermutationHelper _, IEnumerable<int> source)
            => source.ToInt();
        public static int ToInt(this PermutationHelper _, IList<int> source)
            => source.ToInt();
        public static int ToIntUnchecked(this PermutationHelper _, IList<int> source)
            => source.ToIntUnchecked();
        public static IEnumerable<int> ToSeq(this PermutationHelper _, int index, int count)
            => index.ToSeq(count);
        public static int[] ToArray(this PermutationHelper _, int index, int count)
            => index.ToArray(count);

        #endregion

        #region for Long

        public static long ToLong(this PermutationHelper _, IEnumerable<int> source)
            => source.ToLong();
        public static long ToLong(this PermutationHelper _, IList<int> source)
            => source.ToLong();
        public static long ToLongUnchecked(this PermutationHelper _, IList<int> source)
            => source.ToLongUnchecked();
        public static IEnumerable<int> ToSeq(this PermutationHelper _, long index, int count)
            => index.ToSeq(count);
        public static int[] ToArray(this PermutationHelper _, long index, int count)
            => index.ToArray(count);

        #endregion

#if !PCL
        #region for BigInteger

        public static BigInteger ToBigInteger(this PermutationHelper _, IEnumerable<int> source)
            => source.ToBigInteger();
        public static BigInteger ToBigInteger(this PermutationHelper _, IList<int> source)
            => source.ToBigInteger();
        public static BigInteger ToBigIntegerUnchecked(this PermutationHelper _, IList<int> source)
            => source.ToBigIntegerUnchecked();
        public static IEnumerable<int> ToSeq(this PermutationHelper _, BigInteger index, int count)
            => index.ToSeq(count);
        public static int[] ToArray(this PermutationHelper _, BigInteger index, int count)
            => index.ToArray(count);

        #endregion
#endif
    }
}

namespace OmniKits.Utilities
{
    public static class PermutationUtility
    {
#pragma warning disable 1591

        #region for Int

        public static int ToInt(this IEnumerable<int> source)
        {
            return ToInt(source.ToArray());
        }

        public static int ToInt(this IList<int> source)
        {
            if (source.OrderBy(v => v).Where((a, b) => a != b).Any())
                throw new InvalidOperationException();

            return ToIntUnchecked(source);
        }
        public static int ToIntUnchecked(this IList<int> source)
        {
            var result = 0;

            var posList = new int[source.Count];
            for (int i = 0; i < posList.Length; i++)
                posList[i] = i;

            //Array.Reverse(arr);
            var max = source.Count - 1;

            //for (int i = arr.Length - 1; 0 < i; i--)
            for (int i = 0; i < max; i++)
            {
                var ch = source[i];

                result += posList[ch];
                result *= (max - i);

                //p[c] = -1;
                for (int j = ch + 1; j < posList.Length; j++)
                    posList[j]--;
            }

            return result;
        }

        public static IEnumerable<int> ToSeq(this int index, int count)
        {
            var max = count - 1;

            var arr = new int[count + 1];
            for (int i = 2; i < arr.Length; i++)
            {
                var e = index % i;
                if (e > max)
                    throw new InvalidOperationException();
                index /= i;
                arr[i] = e;
            }

            Array.Reverse(arr);

            var result = new int[count];

            var numList = new List<int>(count);
            numList.AddRange(Enumerable.Range(0, count));


            for (int i = 0; i < count; i++)
            {
                var ch = arr[i];
                yield return numList[ch];

                numList.RemoveAt(ch);
            }
        }

        public static int[] ToArray(this int index, int count)
        {
            var tmp = ToSeq(index, count).GetEnumerator();
            var arr = new int[count];
            for (int i = 0; i < arr.Length; i++)
            {
                tmp.MoveNext();
                arr[i] = tmp.Current;
            }
            return arr;
        }

        #endregion

        #region for Long

        public static long ToLong(this IEnumerable<int> source)
        {
            return ToLong(source.ToArray());
        }

        public static long ToLong(this IList<int> source)
        {
            if (source.OrderBy(v => v).Where((a, b) => a != b).Any())
                throw new InvalidOperationException();

            return ToLongUnchecked(source);
        }
        public static long ToLongUnchecked(this IList<int> source)
        {
            var result = 0L;

            var posList = new int[source.Count];
            for (int i = 0; i < posList.Length; i++)
                posList[i] = i;

            //Array.Reverse(arr);
            var max = source.Count - 1;

            //for (int i = arr.Length - 1; 0 < i; i--)
            for (int i = 0; i < max; i++)
            {
                var ch = source[i];

                result += posList[ch];
                result *= (max - i);

                //p[c] = -1;
                for (int j = ch + 1; j < posList.Length; j++)
                    posList[j]--;
            }

            return result;
        }

        public static IEnumerable<int> ToSeq(this long index, int count)
        {
            var max = count - 1;

            var arr = new int[count + 1];
            for (int i = 2; i < arr.Length; i++)
            {
                var e = index % i;
                if (e > max)
                    throw new InvalidOperationException();
                index /= i;
                arr[i] = (int)e;
            }

            Array.Reverse(arr);

            var result = new int[count];

            var numList = new List<int>(count);
            numList.AddRange(Enumerable.Range(0, count));


            for (int i = 0; i < count; i++)
            {
                var ch = arr[i];
                yield return numList[ch];

                numList.RemoveAt(ch);
            }
        }

        public static int[] ToArray(this long index, int count)
        {
            var tmp = ToSeq(index, count).GetEnumerator();
            var arr = new int[count];
            for (int i = 0; i < arr.Length; i++)
            {
                tmp.MoveNext();
                arr[i] = tmp.Current;
            }
            return arr;
        }

        #endregion

#if !PCL
        #region for BigInteger

        public static BigInteger ToBigInteger(this IEnumerable<int> source)
        {
            return ToBigInteger(source.ToArray());
        }

        public static BigInteger ToBigInteger(this IList<int> source)
        {
            if (source.OrderBy(v => v).Where((a, b) => a != b).Any())
                throw new InvalidOperationException();

            return ToBigIntegerUnchecked(source);
        }
        public static BigInteger ToBigIntegerUnchecked(this IList<int> source)
        {
            var result = BigInteger.Zero;

            var posList = new int[source.Count];
            for (int i = 0; i < posList.Length; i++)
                posList[i] = i;

            //Array.Reverse(arr);
            var max = source.Count - 1;

            //for (int i = arr.Length - 1; 0 < i; i--)
            for (int i = 0; i < max; i++)
            {
                var ch = source[i];

                result += posList[ch];
                result *= (max - i);

                //p[c] = -1;
                for (int j = ch + 1; j < posList.Length; j++)
                    posList[j]--;
            }

            return result;
        }

        public static IEnumerable<int> ToSeq(this BigInteger index, int count)
        {
            var max = count - 1;

            var arr = new int[count + 1];
            for (int i = 2; i < arr.Length; i++)
            {
                var e = index % i;
                if (e > max)
                    throw new InvalidOperationException();
                index /= i;
                arr[i] = (int)e;
            }

            Array.Reverse(arr);

            var result = new int[count];

            var numList = new List<int>(count);
            numList.AddRange(Enumerable.Range(0, count));


            for (int i = 0; i < count; i++)
            {
                var ch = arr[i];
                yield return numList[ch];

                numList.RemoveAt(ch);
            }
        }

        public static int[] ToArray(this BigInteger index, int count)
        {
            var tmp = ToSeq(index, count).GetEnumerator();
            var arr = new int[count];
            for (int i = 0; i < arr.Length; i++)
            {
                tmp.MoveNext();
                arr[i] = tmp.Current;
            }
            return arr;
        }

        #endregion
#endif
    }
}

